<?php

namespace common\components;

use common\components\ActionsComponents\UploadOssAction;
use common\components\OSS\AliOSS;
use common\models\Merchant\Merchant;
use OSS\Core\OssException;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\data\Pagination;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use Yii;
use yii\web\UploadedFile;


/**
 * Class DataHelper
 * @package common\components
 * 工具类
 */
class DataHelper
{
    const APP_AGENT = "agent";
    const APP_MANAGE = "manage";
    const APP_BACKEND = "backend";
    const APP_ADMIN = "admin";
    const APP_API = "api";
    const APP_CHAIN = "chain";
    const APP_SALESMAN = "salesman";
    const APP_EMPLOYEE = "employee";
    const APP_FRONTEND = "frontend";
    const APP_CONSOLE = "console";
    const APP_STATIC = "static";

    public static function genRandomString($length = 6)
    {
        // 验证码包含的字符
        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';

        $string = '';

        // 获取 $characters 的长度，并随机截取其中一个字符，直到验证码的长度达到 10 个字符
        for ($p = 0; $p < $length; $p++) {
            $string .= $characters[mt_rand(0, strlen($characters) - 1)];
        }

        // 返回生成的验证码
        return $string;
    }

    public static function getAgeByIdCard($card_id)
    {

        //过了这年的生日才算多了1周岁
        if(empty($card_id)) return null;
        $date = strtotime(substr($card_id,6,8));
        //获得出生年月日的时间戳
        $today = strtotime('today');
        //获得今日的时间戳
        $diff = floor(($today-$date)/86400/365);
        //得到两个日期相差的大体年数

        //strtotime加上这个年数后得到那日的时间戳后与今日的时间戳相比
        $age = strtotime(substr($card_id,6,8).' +'.$diff.'years')>$today?($diff+1):$diff;

        return $age;
    }

    public static function isSn($sn)
    {
        if ( strpos($sn, "69") === 0 && strlen($sn) == 13 ) {
            return true;
        }
        return false;
    }

    public static function memory_get_usage()
    {
        $unit=array('b','kb','mb','gb','tb','pb');
        $size = memory_get_usage(true);
        return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];

    }

    /**
     * 获取请求参数 包含get post
     * @param null $key
     * @param null $default
     * @return array|mixed
     */
    public static function getRequestParams($key = null, $default = null)
    {
        $request = \Yii::$app->request;
        $requestPrams = array_merge($request->get(), $request->getBodyParams());
        if ( $key === null ) {
            return $requestPrams;
        }
        return ArrayHelper::getValue($requestPrams, $key, $default);
    }

    /**
     * 客户端是否在银杏公司
     * @return bool
     */
    public static function isInYxCompany()
    {
        $ip = gethostbyname('aip.wangkaguanli.com');
        if ( \Yii::$app->request->userIP != $ip ) {
            return false;
        }
        return true;
    }

    /**
     * json格式字符串输出
     * @param $json
     * @return string
     */
    public static function jsonFormat($json)
    {
        if ( !is_array($json) ) {
            $json = json_decode($json);
        }
        return json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }

    /**
     * 后两个参数用来调试返回信息的
     * @param array $data
     * @param string $str
     * @param string $sign2
     * @return bool
     */
    public static function signVerifyApiClient(array $data, &$str = "", &$sign2 = "")
    {
        if ( empty($data["tt"]) ) {
            return false;
        }
        $sign = $data["signa"];
        //这里也要卸载 因为客户端验证会多加一个t
        unset($data["signa"],$data["sign"], $data["t"], $data["mch_id"]);
        ksort($data);
        $str = "";
        foreach ( $data as $key => $item ) {
            $str .= "&" . $key . "=" . $item;
        }
        $str = trim($str, "&");
        $str = urlencode($str.$data["tt"]);
        $str = str_replace("_", "%5F", $str);
        $sign2 = md5($str);
        if ( strtolower($sign) == strtolower($sign2) ) {
            return true;
        }
        return false;
    }

    /**
     * v1 大于 v2 return false
     * else
     * return true
     * @param $v1
     * @param $v2
     * @return bool
     */
    public static function checkVersion($v1, $v2)
    {
        $v1 = explode(".", $v1);
        $v2 = explode(".", $v2);

        foreach ( $v1 as $key => $item ) {
            if ( $item > $v2[$key] ) {
                return false;
            }
        }
        return true;
    }

    public static function rc4($data, $pwd)
    {
        $cipher = '';
        $key[] = '';
        $box[] = '';
        $pwd_length = strlen($pwd);
        $data_length = strlen($data);
        for ($i = 0; $i < 256; $i++) {
            $key[$i] = ord($pwd[$i % $pwd_length]);
            $box[$i] = $i;
        }
        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $key[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        for ($a = $j = $i = 0; $i < $data_length; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $k = $box[(($box[$a] + $box[$j]) % 256)];
            $cipher .= chr(ord($data[$i]) ^ $k);
        }
        return $cipher;
    }

    public static function floatSub($num, $digit = 0)
    {
        $num = floatval($num);
        return substr(sprintf("%.".($digit+1)."f", $num), 0, -1);
    }

    public static function getNextLetter($letter, $index = 1)
    {
        return chr(ord($letter) + $index);
    }

    /**
     * 获取当前应用
     * @return bool|string
     */
    public static function getCurrentApp()
    {
        switch ( Yii::$app->id ) {
            case "app-admin":
                return self::APP_ADMIN;
            case "app-agent":
                return self::APP_AGENT;
            case "app-backend":
                return self::APP_BACKEND;
            case "app-api":
                return self::APP_API;
            case "app-salesman":
                return self::APP_SALESMAN;
            case "app-employee":
                return self::APP_EMPLOYEE;
            case "manage":
                return self::APP_MANAGE;
            case "app-frontend":
                return self::APP_FRONTEND;
            case "app-console":
                return self::APP_CONSOLE;
        }
        return false;
    }

    public static function strReplaceOnce($needle, $replace, $haystack) {
            // Looks for the first occurence of $needle in $haystack
            // and replaces it with $replace.
            $pos = strpos($haystack, $needle);
            if ($pos === false) {
            // Nothing found
            return $haystack;
        }
        return substr_replace($haystack, $replace, $pos, strlen($needle));
    }


    public static function calc($size,$digits=2)
    {
        if ( !$size ) {
            return 0;
        }
        $unit = ['','K','M','G','T','P'];
        $base= 1024;
        $i = floor(log($size,$base));
        $n = count($unit);
        if($i >= $n){
            $i=$n-1;
        }
        return round($size/pow($base,$i),$digits).' '.$unit[$i] . 'B';
    }

    /**
     * 将秒数转化为n小时n分钟
     * @param $sec
     * @return string
     */
    public static function sec2time($sec)
    {
        $sec = round($sec / 60);
        if ( $sec >= 60 ) {
            $hour = floor($sec / 60);
            $min = $sec%60;
            $res = $hour.'小时 ';
            $min != 0  &&  $res .= $min.'分';
        } else {
            $res = $sec.'分钟';
        }

        return $res;
    }

    public static function getFileExtension($file)
    {
        return substr($file, strrpos($file, '.')+1);
    }


    /**
     * 求两个日期之间相差的天数
     * (针对1970年1月1日之后，求之前可以采用泰勒公式)
     * @param string $day1
     * @param string $day2
     * @return number
     */
    public static function diffBetweenTwoDays($day1, $day2)
    {
        if ( !is_numeric($day1) ) {
            $second1 = strtotime($day1);
        } else {
            $second1 = $day1;
        }

        if ( !is_numeric($day2) ) {
            $second2 = strtotime($day2);
        } else {
            $second2 = $day2;
        }

        $times = abs($second1 - $second2);

        return $times / 86400;
    }


    /**
     * 判断一个值是否空值
     * 有如下情况：
     * 1, `null`
     * 2, 空字符串 `''`
     * 3, 只包括空白的字符串
     * 4, 空数组
     *
     * @param $value
     * @return bool
     */
    public static function isEmpty($value)
    {
        return $value === '' || $value === [] || $value === null || is_string($value) && trim($value) === '';
    }

    public static function getCurrentVersion()
    {
        return 1;
    }

    public static function ip2long($ipStr)
    {
        return bindec(decbin(ip2long($ipStr)));
    }

    public static function long2ip($long) {
        // Valid range: 0.0.0.0 -> 255.255.255.255
        if ($long < 0 || $long > 4294967295) return false;
        $ip = "";
        for ($i=3;$i>=0;$i--) {
            $ip .= (int)($long / pow(256,$i));
            $long -= (int)($long / pow(256,$i))*pow(256,$i);
            if ($i>0) $ip .= ".";
        }
        return $ip;
    }


    public static function getHeaderImageByQQ($qq)
    {
        return sprintf("http://q2.qlogo.cn/headimg_dl?dst_uin=%s&spec=100", $qq);
    }

    public static function getDefaultImage()
    {
        return GlobalUrlComponent::buildStaticUrl("/default.png");
    }

    public static function getDefaultHeaderImage()
    {
        return GlobalUrlComponent::buildStaticUrl("/default_header.jpg");
    }

    /**
     * 过滤属性
     * @param array $retainAttribute
     * @param array $attributes
     * @param bool $isPad 是否填充 针对需要保留的值如果不在传入属性中 则自动填充padValue
     * @param null $padValue 填充值
     * @return array
     */
    public static function filterAttribute(array $retainAttribute, array $attributes, $isPad = false, $padValue = null)
    {
        $newAttribute = [];
        foreach ( $retainAttribute as $item ) {
            if ( isset($attributes[$item]) ) {
                $newAttribute[$item] = $attributes[$item];
            } else {
                if ( $isPad ) {
                    $newAttribute[$item] = $padValue;
                }
            }
        }
        return $newAttribute;
    }

    /**
     * 检测链接是否是SSL连接
     * @return bool
     */
    public static function  is_SSL()
    {
        if (!isset($_SERVER['HTTPS']))
        {
            return FALSE;
        }

        if ($_SERVER['HTTPS'] === 1)
        {  //Apache
            return TRUE;
        }
        else if ($_SERVER['HTTPS'] === 'on')
        { //IIS
            return TRUE;
        }
        elseif ($_SERVER['SERVER_PORT'] == 443)
        { //其他
            return TRUE;
        }

        return FALSE;
    }

	/**
	 * 返回手机验证正则
	 * 用于Model rule
	 * @return string
	 */
	public static function patternMobile()
	{
		return '/^1\d{10}$/';
	}

	public static function patterPhone()
    {
        return "/^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$|^((\(\d{2,3}\))|(\d{3}\-))?1\d{10}$/";
    }

    public static function encode($string)
    {
        return  Html::encode($string);
    }

	/**
     * 获取分页
     * @param $iCount
     * @param int $pageSize
     * @return Pagination
     */
    public static function getPage($iCount, $pageSize=20)
    {
        $oPage = new Pagination([
            'totalCount' => $iCount,
            'defaultPageSize' => $pageSize,
            'pageSizeParam' => 'per',
            'validatePage' => false,
        ]);
        $oPage->pageSizeLimit = [1,9999];
        return $oPage;
    }

    /**
     * 获取数据供给器的分页参数数组
     * @param $page
     * @param $params
     * @return array
     */
    public static function getDataProviderPageParams($page, array $params = [])
    {
        return [
            'pagination'=>array_merge([
                'pageSize' => $page,
                'pageSizeParam' => false,
                'validatePage' => false
            ], $params)
        ];
    }

	/**
	 * 获取数据供给者
	 * @param $query
	 * @param array $params
	 * @return ActiveDataProvider
	 */
	public static function getActiveDataProvider($query, array $params = [])
	{
		$params = array_merge([
			'pagination'=>[
				'pageSize' => self::qVal('size','%d{1,50}', 15, Yii::$app->request->get()),
                'pageParam'=>'page', //page=1
				'pageSizeParam' => 'size', //size=10
				'validatePage' => false
			]
		], $params);

		$params['query'] = $query;
		return new ActiveDataProvider($params);
	}

    /**
     * 获取分页链接 <对于前端>
     * @param Pagination $page
     * @return string
     */
    public static function getLinkPager(Pagination $page)
    {
        $linkPage =  \yii\widgets\LinkPager::widget([
            'pagination' => $page,
            'firstPageLabel' => '首页',
            'lastPageLabel' => '尾页',
          //  'nextPageLabel' => '下一页',
           // 'prevPageLabel' => '上一页'
        ]);

        $div = sprintf(
            '<div class="pageinfo" style="float:left; height:75px; line-height:75px; margin-right:10px;">共%d条记录｜每页%d条</div>',
            $page->totalCount,
            $page->getPageSize()
        );

        return $div.$linkPage;
    }

	/**
	 * @param array $arr 接收数组
	 * @param int $pid 父级id
	 * @param bool $tree 树状返回数组 默认为false 不返回树状
	 * @param array $config
	 * @param int $level
	 * @return array
	 */
	public static function common_recursion_arr(array $arr, $pid=0, $tree=false, array $config = [], $level=0)
	{
		#定义递归返回数组
		$arrReturn = [];
        $config = array_merge([
            'pid'=>'pid',
            'id'=>'id',
            'tag'=>null,
            'children_name' => 'children'
        ], $config);
		
		static $mark = '—';
		
		if( $tree == false ) {
			foreach ( $arr as $k=>$v ) {
				//非树状
				if( $v[$config['pid']] == $pid ) {
					if(isset($config['tag']) && !empty($config['tag'])) {
						$v[$config['tag']] = str_repeat($mark, $level).$v[$config['tag']];
					}

					$arrReturn[] = $v;
					$arrReturn = array_merge($arrReturn, self::common_recursion_arr($arr, $v[$config['id']], $tree, $config, $level+1));
				}
			}
		} else {
		    $children_name = isset($config['children_name']) && $config['children_name'] ? $config['children_name'] : 'children';

			foreach ( $arr as $k=>$v ) {
				//树状
				if( $v[$config['pid']] == $pid ) {
					$arrReturn[$k] = $v instanceof Model ? $v->toArray() : $v;
					$arrReturn[$k][$children_name] = self::common_recursion_arr($arr, $v[$config['id']], $tree, $config);
					if ( !$arrReturn[$k][$children_name] ) {
					    unset($arrReturn[$k][$children_name]);
                    } else {
                        $arrReturn[$k][$children_name] = array_values($arrReturn[$k][$children_name]);
                    }
				}
			}
		}
		
		return $arrReturn;
	}

    /**
     * 字符串截取 一般用于在列表页显示详情限制字数
     * @param $str
     * @param $len
     * @param string $suffix
     * @return string
     */
	public static function common_substrByLen($str, $len, $suffix='...')
	{
		if ( mb_strlen($str, 'utf-8') > $len ) return mb_substr($str, 0, $len, 'utf-8').$suffix;
		
		return $str;
	}

    /**
     * 获取配置文件信息
     * @param string $key
     * @param string $default
     * @return mixed
     */
	public static function getConfig($key='ALL', $default = null)
	{
		if ( $key === 'ALL' ) {
		    return Yii::$app->params;
        }

		return ArrayHelper::getValue(Yii::$app->params, $key, $default);
	}


	public static function uploadFileContent($content, $saveAs, array $arrAllowType = ['image/jpeg', 'image/png'], $size=0.5, $filename = '')
    {
        if ( !$content ) {
            return [
                "status" => false,
                "msg" => "上传内容不能为空"
            ];
        }
        $fileSize = strlen($content);
        $_size = $size * 1024 * 1024;
        if ( $fileSize > $_size ) return [
            'status' => false,
            'msg' => '上传文件超出大小，请最大上传'.$size.'M的文件'
        ];

        //这里来校验一下指定的类型
        $fileInfo = finfo_open();
        $mimeType = finfo_buffer($fileInfo, $content, FILEINFO_MIME_TYPE);

        if ( $arrAllowType && !in_array($mimeType, $arrAllowType) ) {
            return [
                'status' => false,
                'msg' => '请上传:'.implode(',', $arrAllowType).'类型的文件'
            ];
        }

        if ( !$filename ) {
            $filename = md5(uniqid()) . '.jpg';
        } else {
            $filename = $filename . '.jpg';
        }

        $filename = sprintf("%s/%s", trim($saveAs, '/'), $filename);

        $aliOss = new AliOSS();
        $ossClient = $aliOss->initOssClient();
        try {
            $uploadRet = $ossClient->putObject($aliOss->getConfig('bucket'), $filename, $content);#die(print_r($uploadRet));
        } catch ( OssException $e ) {
            return [
                'status' => false,
                'msg' => $e->getMessage()
            ];
        }

        return [
            'status' => true,
            'url' => $uploadRet['oss-request-url'],
            "object" => $filename
        ];
    }

    /**
     * 图片上传
     * @param $uploadName
     * @param $saveAs
     * @param bool $isAllowEmpty
     * @param array $arrAllowType
     * @param float $size
     * @param string $filename
     * @param bool $useBase64  true base64上传 或按文件名获取后缀
     * @return array|null
     */
    public static function uploadFile($uploadName, $saveAs, $isAllowEmpty=false, array $arrAllowType = ['image/jpeg', 'image/png'], $size=0.5, $filename = '', $useBase64=false)
    {
        if($useBase64===true){
            $file = new \stdClass();
            $file->content = str_replace([
                "data:image/png;base64,",
                "data:image/jpeg;base64,",
                " "
            ], "", $uploadName);
            $file->content = base64_decode($file->content);
            $file->size = strlen($file->content);
            $file->name = self::SN18();
            $file->extension = '';
        }else{
            $file = UploadedFile::getInstanceByName($uploadName);
        }

        if ( !$file ) {
            return $isAllowEmpty ? null : [
                'status' => false,
                'msg' => '未找到上传文件'
            ];
        }

        $_size = $size * 1024 * 1024;
        if ( $file->size > $_size ) return [
            'status' => false,
            'msg' => '上传文件超出大小，请最大上传'.$size.'M的文件'
        ];

        $extension = $file->extension;
        if($useBase64){ //这里来校验一下指定的类型
            $fileInfo = finfo_open();
            if($useBase64===true){ //按文件内容分析获取扩展名
                $file->type = finfo_buffer($fileInfo, $file->content, FILEINFO_MIME_TYPE);
                $extension = UploadOssAction::getSuffixByMimeType($file->type);
            }else{ //按文件名的后缀
                $file->type = finfo_buffer($fileInfo, file_get_contents($file->tempName), FILEINFO_MIME_TYPE);
                $extension = strtolower(pathinfo($useBase64, PATHINFO_EXTENSION));
            }
        }

        if ( $arrAllowType && !in_array($file->type, $arrAllowType) ) return [
            'status' => false,
            'msg' => '请上传:'.implode(',', $arrAllowType).'类型的文件'
        ];

        if ( !$filename ) {
            $filename = md5($file->name.uniqid()) . '.' . $extension;
        } else {
            $filename = $filename . '.' . $extension;
        }

        $aliOss = new AliOSS();
        $ossClient = $aliOss->initOssClient();

        $object = sprintf("%s/%s", trim($saveAs, '/'), $filename);
        try {
            if($useBase64===true){
                $uploadRet = $ossClient->putObject($aliOss->getConfig('bucket'), $object, $file->content);
            }else{
                $uploadRet = $ossClient->uploadFile($aliOss->getConfig('bucket'), $object, $file->tempName);
            }
        } catch ( OssException $e ) {
            return [
                'status' => false,
                'msg' => $e->getMessage()
            ];
        }

        return [
            'status' => true,
            'url' => $uploadRet['oss-request-url']
        ];
    }

    public static function uploadFileAsLocal($uploadName, $saveAs, $isAllowEmpty=false, array $arrAllowType = ['image/jpeg', 'image/png'], $size=0.5, $filename = '')
    {
        $file = UploadedFile::getInstanceByName($uploadName);

        if ( !$file ) {
            return $isAllowEmpty ? null : [
                'status' => false,
                'msg' => '未找到上传文件'
            ];
        }
        if($file->error){
            $fail = '';
            switch($file->error){
                case UPLOAD_ERR_INI_SIZE:
                    $fail = "上传文件大小超出了配置文件中的约定值";
                    break;
                case UPLOAD_ERR_FORM_SIZE:
                    $fail = "上传文件大小超出了表单中的约定值";
                    break;
                case UPLOAD_ERR_PARTIAL:
                    $fail = "文件只有部分被上传";
                    break;
                case UPLOAD_ERR_NO_FILE:
                    $fail = "没有文件被上传";
                    break;
                case UPLOAD_ERR_NO_TMP_DIR:
                    $fail = "找不到临时文件夹";
                    break;
                case UPLOAD_ERR_CANT_WRITE:
                    $fail = "文件写入失败";
                    break;
                default:
                    $fail = "未知错误";
            }
            return [
                'status' => false,
                'msg' => $fail
            ];
        }

        $_size = $size * 1024 * 1024;
        if ( $file->size > $_size ) return [
            'status' => false,
            'msg' => '上传文件超出大小，请最大上传'.$size.'M的文件'
        ];

        if ( !in_array($file->type, $arrAllowType) ) return [
            'status' => false,
            'msg' => '请上传:'.implode(',', $arrAllowType).'类型的文件'
        ];

        if ( !$filename ) {
            $filename = md5($file->name.uniqid()) . '.' . $file->extension;
        } else {
            $filename = $filename . '.' . $file->extension;
        }

        if ( !is_array($saveAs) ) {
            $saveAs = trim($saveAs, '/');
            $upPath = \Yii::getAlias("@static") . '/web' . "/" . $saveAs;
            $uploadApp = self::APP_STATIC;
        } else {
            $uploadApp = $saveAs["app"];
            $saveAs["path"] = trim($saveAs["path"], '/');
            $upPath = rtrim(\Yii::getAlias("@$uploadApp"), "/") . "/web/" . $saveAs["path"];
            $saveAs = $saveAs["path"];
        }

        #如果目录不存在 则创建
        if ( !file_exists($upPath) ) {
            mkdir($upPath, 0755, true);
        }

        $upFile = sprintf("%s/%s", $upPath, $filename);

        $arrReturn = $file->saveAs($upFile) ? [
            'status'=>1,
            'uploadInfo'=>$upFile,
            'saveAs' => GlobalUrlComponent::buildUrl($uploadApp, "/" . $saveAs . "/" . $filename),
            'ori_name'=>$file->name,
            'filename'=>$filename,
            'extension'=>$file->extension,
            'type'=>$file->type
        ] : [
            'status'=>0,
            'msg'=>'文件上传失败'
        ];

        return $arrReturn;
    }


    /**
     * 生产唯一订单号
     * @param $prefixId
     * @return string
     */
	public static function createOrderSn($prefixId = 0)
	{
	    if ( !$prefixId ) {
            $prefixId = Merchant::getCurrentMerInfo('id');
        }
        $chars = substr(microtime(),2,6);
        $sn = date('YmdHis').substr(str_shuffle($chars.$chars),0,6); //20位
        return '8' . str_pad($prefixId, 7, '0', STR_PAD_LEFT) . $sn;
	}

    /**
     * @param null $time
     * @param string $format
     * @return false|string
     */
    public static function date($time = null, $format = 'Y-m-d H:i:s')
    {
        if ( !$time && $time !== null ) {
            return false;
        }

        return !$time ? date($format) : date($format, intval($time));
    }

    /**
     * 判断是否包含value
     * @param $value
     * @param $original
     * @param string $delimiter
     * @return bool
     */
    public static function isSelect($value, $original, $delimiter = ',')
    {
        if ( !$original ) {
            return false;
        }

        $original = explode($delimiter, $original);

        if ( !$original ) {
            return false;
        }

        if ( !in_array($value, $original) ) {
            return false;
        }

        return true;
    }

    public static function gmtIso8601($timestamp)
    {
        $dtStr       = date('c', $timestamp);
        $dt          = new \DateTime($dtStr);
        $newDatetime = $dt->format(\DateTime::ISO8601);
        $pos         = strpos($newDatetime, '+');
        $newDatetime = substr($newDatetime, 0, $pos);

        return $newDatetime . 'Z';
    }
    public static function useArrColumnAsIndex(array $array, $column)
    {
        $ret = [];
        foreach ( $array as $item ) {
            if ( isset($item[$column]) ) {
                $ret[$item[$column]] = $item;
            }
        }
        return $ret;
    }
    //转换成json
    public static function toJson($res, $option=0){
        if($option==0 && defined('JSON_UNESCAPED_UNICODE'))
            $option = JSON_UNESCAPED_UNICODE;
        return json_encode($res, $option);
	}
    //按商户id以1000为基准生成目录
    public static function mch_dir($mch_id, $dir='') {
        return intval($mch_id/1000).'/'.$mch_id.'/'.($dir!=''?trim($dir, '/').'/':'');
    }
    //递归自定方法数组处理 传址
    public static function array_call_func($func, &$data){
        //$r=array();
        foreach ($data as $key => $val) {
            $data[$key] = is_array($val)
             ? self::array_call_func($func, $val)
             : call_user_func($func, $val);
        }
        return $data;
    }
    public static $qBefore;
    public static $qAfter;
    public static $filter = 'htmlspecialchars';

    /** qVal 取值过滤验证
     * @param string $data 数据或数组键名
     * @param string $rule   %s,%b,%d,%f,%a,%date[2014-01-11 13:23:32],%his[13:23:32]  {1,20}取值范围 filter:fun1,fun2,/regx/i正则过滤
     * @param string $defval 默认值
     * @param null $datas 指定数据源 数组
     * @return array|float|int|mixed|null|string
     */
    public static function qVal($data, $rule='', $defval='',$datas=null) {
        $filter = $min = $max = null;
        $type = 's'; // 默认转换为字符串
        if($rule!=''){
            if(strpos($rule,':')!==false){ // 指定过滤方法
                list($rule,$filter) = explode(':',$rule,2);
            }
            if (strpos($rule, '{')!==false && substr($rule, -1) == '}') { // 指定长度范围 用于数字及字符串
                list($rule, $size) = explode('{', substr($rule, 0, -1), 2);
                if (strpos($size, ',')) {
                    list($min, $max) = explode(',', $size, 2);
                } else $max = $size;
            }
            if ($rule != '') { // 指定修饰符
                $type = strpos($rule, '%')!==false ? substr($rule, 1) : $rule;
            }
        }
        if (is_array($datas)) {
            if(strpos($data,'.')){
                $names = explode('.',$data);
                foreach ($names as $name){
                    if(isset($datas[$name])) {
                        $data = $datas[$name];
                        $datas = $data;
                    }else{
                        $data = null;break;
                    }
                }
                if($data!==null) $data = $datas;
            }else{
                $data = isset($datas[$data]) ? $datas[$data] : null;
            }
        }
        if($data!==null) { // 取值操作
            $data = is_array($data) ? $data : trim($data);
            if(self::$qBefore instanceof \Closure){
                $fun = self::$qBefore;
                $data = $fun($data);
            }
            $filter = isset($filter)?$filter:self::$filter;
            if($filter && $filter!='null') { //过滤处理
                if(is_string($filter)){
                    if(0 === strpos($filter,'/')){ //正则验证处理
                        if(1 !== preg_match($filter,(string)$data)){// 支持正则验证
                            return $defval;
                        }
                    }else{ //多个过滤,分隔
                        $filters = strpos($filter, ',')===false ? array($filter) : explode(',', $filter);
                    }
                }elseif(is_int($filter)){
                    $filters = array($filter);
                }

                if(isset($filters)){
                    foreach($filters as $filter){
                        if(function_exists($filter)) {
                            $data = is_array($data) ? self::array_call_func($filter,$data) : $filter($data); // 参数过滤
                        }else{ // ?? filter_var
                            $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
                            if(false === $data) {return $defval;}
                        }
                    }
                }
            }
            /*
            (int), (integer) - 转换为整形 integer	(bool), (boolean) - 转换为布尔类型 boolean	(float), (double), (real) - 转换为浮点型 float
            (string) - 转换为字符串 string	(array) - 转换为数组 array	(object) - 转换为对象 object	(unset) - 转换为 NULL (PHP 5)
            */
            //类型转换
            switch($type){
                case 'a':	// 数组
                    $data =	(array)$data;
                    break;
                case 'd':	// 数字
                case 'f':	// 浮点
                    if ($data=='' || !is_numeric($data)) {$data = $defval==''?0:$defval; break;}
                    $defval = $type=='d'?(int)$defval:(float)$defval;
                    $data =	$type=='d'?(int)$data:(float)$data;
                    if($max!=null && $min!=null) $data = $min<=$data && $data<=$max ? $data : $defval;
                    elseif($max!=null) $data = $data<=$max ? $data : $defval;
                    elseif($min!=null) $data = $min<=$data ? $data : $defval;
                    break;
                case 'b':	// 布尔
                    $data =	(boolean)$data; break;
                case 'date':// 日期
                    $data =	self::IsTime($data) ? $data : $defval; break;
                case 'ymd':
                    $data =	self::IsYmd($data) ? $data : $defval; break;
                case 'his':
                    $data =	self::IsHis($data) ? $data : $defval; break;
                case 's':   // 字符串
                default:
                    if($data=='') {$data = $defval; break;}
                    $data = (string)$data;
                    $len = strlen($data);
                    if($max!=null && $min!=null) $data = $min<=$len && $len<=$max ? $data : $defval;
                    elseif($max!=null) $data = $len<=$max ? $data : self::cutstr($data,$max);
                    elseif($min!=null) $data = $min<=$len ? $data : $defval;

                    //if($type=='e') //不作转义处理 5.4.0起已移除
                    //    if (get_magic_quotes_gpc()) $data = stripslashes($data);
            }
        }
        else{ // 变量默认值
            $data = $defval;
            if($type=='d' || $type=='f'){
                $data =	$type=='d'?(int)$data:(float)$data;
            }
        }
        if(self::$qAfter instanceof \Closure){
            $fun = self::$qAfter;
            $data = $fun($data);
        }
        return $data;
    }
    //替换字符串中间位置字符为星号  仅英文字符
    public static function half_replace($str){
        $len = strlen($str)/2;
        return substr_replace($str,str_repeat('*',$len),ceil(($len)/2),$len);
    }
    //替换字符串中间位置字符为星号  仅英文字符
    public static function half2_replace($str){
        $len = ceil(strlen($str)/3);
        $cLen = floor(strlen($str)/2-($len/2));
        return substr_replace($str, str_repeat('*',$len), $cLen, $len);
    }
    public static function cn_half_replace($str){
        preg_match_all('/./u', $str, $arr);
        $len = count($arr[0])/2;
        $offset = ceil(($len)/2);
        $a = implode('', array_slice($arr[0], 0, $offset));
        $b = implode('', array_slice($arr[0], $offset+$len));
        return $a.str_repeat('*',$len).$b;
    }
    //字符截取 $string中汉字、英文字母、数字、符号每个在$len中占一个数，不存在汉字占两个字节的考虑
    public static function cutstr($str, $len, $suffix = '') {
        if (function_exists('mb_substr')){
            $str = mb_substr($str, 0, $len, 'UTF-8').(mb_strlen($str)>$len?$suffix:'');
        }
        else{
            preg_match_all('/./u', $str, $arr);//su 匹配所有的字符，包括换行符 /u 匹配所有的字符，不包括换行符
            //var_dump($arr);
            $str = implode('', array_slice($arr[0], 0, $len));
            if (count($arr[0]) > $len) {
                $str .= $suffix;
            }
        }
        return $str;
    }

    public static function filterEmoji($str){
        return preg_replace_callback('/./u', function ($match) {return strlen($match[0]) >= 4 ? '' : $match[0];}, $str);
    }
    //是否微信
    public static function IsWeixin(){
        if ( isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false ) {
            return true;
        }
        return false;
    }
    //日期检测函数(格式:2007-5-6[ 15:30[:33]])
    public static function IsTime($date) {
        return preg_match('/^(\d{4})-(0[1-9]|[1-9]|1[0-2])-(0[1-9]|[1-9]|1\d|2\d|3[0-1])(| (0[0-9]|[0-9]|1[0-9]|2[0-3]):([0-5][0-9]|0[0-9]|[0-9])(|:([0-5][0-9]|0[0-9]|[0-9])))$/', $date);
    }
    //His检测函数(格式:2007-5[-6])
    public static function IsYmd($date) {
        return preg_match('/^(\d{4})-(0[1-9]|[1-9]|1[0-2])(|-(0[1-9]|[1-9]|1\d|2\d|3[0-1]))$/', $date);
    }
    //His检测函数(格式:15:30[:33])
    public static function IsHis($date) {
        return preg_match('/^(0[0-9]|[0-9]|1[0-9]|2[0-3]):([0-5][0-9]|0[0-9]|[0-9])(|:([0-5][0-9]|0[0-9]|[0-9]))$/', $date);
    }
    //判断email格式是否正确
    public static function IsEmail($email) {
        return strlen($email) > 6 && preg_match("/^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/", $email);
    }
    //判断是否手机号
    public static function IsMobile($mobile){
        return strlen($mobile)==11 && preg_match("/^1[3456789]\d{9}$/", $mobile);
    }
    /** 身份解析 ['sex'=>'','birth'=>'', 'province'=>'']|null
     * @param $id
     * @return array|null
     */
    public static function decodeIdCard($id){
        $aWeight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; //十七位数字本体码权重
        $aValidate = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; //mod11,对应校验码字符值

        if (strlen($id) != 18) return null;
        $pIdx = substr($id, 0, 2);
        $province = array(
            11 => "北京", 12 => "天津", 13 => "河北", 14 => "山西", 15 => "内蒙古", 21 => "辽宁",
            22 => "吉林", 23 => "黑龙江", 31 => "上海", 32 => "江苏", 33 => "浙江", 34 => "安徽",
            35 => "福建", 36 => "江西", 37 => "山东", 41 => "河南", 42 => "湖北", 43 => "湖南",
            44 => "广东", 45 => "广西", 46 => "海南", 50 => "重庆", 51 => "四川", 52 => "贵州",
            53 => "云南", 54 => "西藏", 61 => "陕西", 62 => "甘肃", 63 => "青海", 64 => "宁夏",
            65 => "新疆", 71 => "台湾", 81 => "香港", 82 => "澳门", 91 => "国外"
        );
        if (!isset($province[$pIdx])) return null;

        $Y = substr($id, 6, 4);
        $m = substr($id, 10, 2);
        $d = substr($id, 12, 2);
        $iDate = $Y . '-' . $m . '-' . $d;
        $rPattern = '/^((19|20)[0-9]{2})-(0[1-9]|1[0-2])-(0[1-9]|1\d|2\d|3[0-1])$/';
        if (!preg_match($rPattern, $iDate, $arr)) return null;

        // 依据前17位计算校验码
        $sum = 0;
        for ($i = 0; $i < 17; $i++) {
            $sum += $id[$i] * $aWeight[$i];
        }
        $mode = $sum % 11;
        $code = $aValidate[$mode];
        if ($code != substr($id, 17, 1)) return null;

        $sexInt = (int)substr($id, 16, 1);
        return ['id' => $id, 'province' => $province[$pIdx], 'birth' => "{$Y}-{$m}-{$d}", 'sex' => $sexInt % 2 === 0 ? '女' : '男'];
    }
    /**
     * 产生随机字符串
     *
     * @param   int        $len  输出长度
     * @param   string     $chars 字符串 0:数字 1:仅字母 01:数字字母混合
     * @return  string
     */
    public static function random($len=6, $chars='0') {
        if($chars=='0') $chars = '0123456789';
        elseif($chars=='1') $chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPRSTUVWXYZ';
        elseif($chars=='01') $chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPRSTUVWXYZ0123456789';
        return substr(str_shuffle($chars.$chars), 0, $len);
    }
    //生成短信验证码 $id 标识 $tel待发送的手机号
    public static function smsCode($id, $tel){
        $code = DataHelper::random(6);
        $code = '111111';//test
        \Yii::$app->redis->setex('code_mobile:'.$id, 600, $code); //缓存10分钟
        //todo 发送短信
        //send code;
    }
    //验证短信验证码 $id 标识 $code待验证的验证
    public static function smsChkCode($id, $code){
        $redis_code = \Yii::$app->redis->get('code_mobile:'.$id);
        if(!$redis_code) return null;
        if($redis_code!=$code) {
            if(!$err = \Yii::$app->redis->get('code_mobile_err:'.$id)){
                \Yii::$app->redis->setex('code_mobile_err:'.$id, 600, 1);
            }else{
                \Yii::$app->redis->incr('code_mobile_err:'.$id);
            }
            if($err==5){ //输入错误超过6次  清除缓存
                \Yii::$app->redis->del('code_mobile:'.$id, 'code_mobile_err:'.$id);
                return 0;
            }
            return false;
        }
        \Yii::$app->redis->del('code_mobile:'.$id, 'code_mobile_err:'.$id); //清除缓存
        //次数限制
        return true;
    }
    //用于输出
    public static function out($status=1,$msg="ok",$data=null){
        return ['status'=>$status,'msg'=>$msg,'data'=>$data];
    }
    // 跳转提示信息输出: ([0,1]:)信息标题, url, 辅助信息, 等待时间（秒） 用于前端自动定义信息输出模板
    public static function outMsg($message, $url='', $info='', $time = 1) {
        if ($url=='') {
            $jumpUrl = 'javascript:window.history.back()';
            $js = 'window.history.back()';
        } elseif (substr($url,0,11)=='javascript:') {
            $jumpUrl = $url;
            $js = substr($url,11);
        } else {
            $jumpUrl = $url;
            $js = "window.location='$jumpUrl'";
        }
        $status = substr($message,0,1); //提示状态 默认为普通
        $flag = 'normal'; //普通提示
        if($status=='1' || $status=='0'){
            $message=substr($message,2);
            $flag = $status=='1'?'success':'error'; //成功提示 | 错误提示
        }else{
            $status = -1;
        }

        if(ob_get_length() !== false) ob_clean();//清除页面
        if(\Yii::$app->request->isAjax){ //ajax输出
            $json = array('code'=>$flag=='error'?1:0, 'msg'=>$message, 'url'=>$jumpUrl, 'info'=>$info, 'time'=>$time);
            exit(self::toJson($json));
        }

        $out_html = '<!doctype html><html><head><meta charset="utf-8"><title>'.($url!='nil'?'跳转提示':'信息提示').'</title><style type="text/css">*{padding:0;margin:0}body{background:#fff;font-family:"Microsoft YaHei";color:#333;font-size:100%}.system-message{padding:1.5em 3em}.system-message h1{font-size:6.25em;font-weight:400;line-height:120%;margin-bottom:.12em}.system-message .jump{padding-top:.625em}.system-message .jump a{color:#333}.system-message .success{color:#207E05}.system-message .error{color:#da0404}.system-message .normal,.system-message .success,.system-message .error{line-height:1.8em;font-size:2.25em}.system-message .detail{font-size:1.2em;line-height:160%;margin-top:.8em}</style></head><body><div class="system-message">';

        $out_html .= '<h1>'.($status?':)':':(').'</h1><p class="'.$flag.'">'.$message.'</p>'; //输出

        $out_html .= $info!=''?'<p class="detail">'.$info.'</p>':'';
        if($url!='nil') //提示不跳转
            $out_html .= '<p class="jump">页面自动 <a id="href" href="'.$jumpUrl.'">跳转</a>  等待时间： <b id="time">'.$time.'</b><!--<br><a href="'.$jumpUrl.'">如未跳转请点击此处手工跳转</a>--></p></div><script type="text/javascript">var pgo=0,t=setInterval(function(){var time=document.getElementById("time");var val=parseInt(time.innerHTML)-1;time.innerHTML=val;if(val<=0){clearInterval(t);if(pgo==0){pgo=1;'.$js.';}}},1000);</script></body></html>';

        exit($out_html);
    }
    /** 二维数组排序 array_sort
     * @param array $array_name 传入的数组
     * @param string$row_id 数组想排序的项
     * @param string $order_type 排序的方式
     * @param bool $auto 是否开启自动键值（默认开启，字符键值时可以关闭）
     * @return array
     */
    public static function array_sort($array_name,$row_id,$order_type='asc',$auto=TRUE){
        $array_temp=[];
        foreach($array_name as $key=>$value){//循环一层；
            $array_temp[$key] = $value[$row_id];//新建一个一维的数组，索引值用二维数组的索引值；值为二维数组要比较的项目的值；
        }
        if($order_type=="asc"){
            asort($array_temp);
        }else{
            arsort($array_temp);
        }
        $result_array=[];
        if($auto){
            foreach($array_temp as $key=>$value){//对进行筛选过的数组遍历；s
                $result_array[]=$array_name[$key];//新建一个结果数组，将原来传入的数组改变键值顺序后赋值给结果数组（原来数组不变）；
            }
        }else{
            foreach($array_temp as $key=>$value){
                $result_array[$key]=$array_name[$key];//新建一个结果数组，将原来传入的数组按原有的键值顺序后赋值给结果数组（原来数组不变）；
            }
        }
        return $result_array;
    }
    public static function json_filter($string) {
        $string = str_replace(array('%20','%27','%2527',"'","\\"),'',$string);
        return $string;
    }
    //转换成xml
    public static function toXml($res,$rec=false){
        $xml = $rec ? '' : '<root>';
        foreach ($res as $k => $v) {
            if (is_array($v)) {
                $xml .= '<' . $k . '>' . self::toXml($v, true) . '</' . $k . '>';
            } else {
                $xml .= '<' . $k . '>' . (is_numeric($v) ? $v : '<![CDATA[' . $v . ']]>') . '</' . $k . '>';
            }
        }
        $xml .= $rec ? '' : '</root>';
        unset($res);
        return $xml;
    }
    //将XML转为array
    public static function xmlToArr($xml){
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    //组合where
    public static function makeWhere($mch_id, $id=0){
        $where = '';
        if(is_numeric($id)){
            return $where;
        }
        elseif(is_array($id)){
            $where = ['and'];
            $where[] = ['=','mch_id',$mch_id];

            //$id['mch_id'] = $mch_id;
            foreach ($id as $k=>$v){
                $operator = '=';
                if($pos=strpos($k,'::')){
                    $operator = trim(substr($k, $pos+2));
                    $operator = $operator == '' ? '=' : $operator;
                    $k = substr($k,0, $pos);
                }
                $where[] = [$operator, $k, $v];
                //$where[] = ['=', $k, $v];
                /*
                if ( is_string($v) ) {
                    $where .= sprintf(" and %s='%s'", $k, htmlspecialchars ($v, ENT_QUOTES));
                } else {
                    $where .= sprintf(" and %s=%s", $k, $v);
                }*/
            }
            //$where = substr($where,4);
            $where = self::toJson($where);
        }elseif(is_string($id)){
            $where = '';
            //$where = $id;
        }
        return $where;
    }
    const FRP_STATUS = 2;
    //本地服务端是否登陆
    public static function is_local_login($mch_id){
        $token = \Yii::$app->redis->get('tcpToken:'.$mch_id);
        $val = \Yii::$app->redis->hget('uid_fd', $mch_id);
        Yii::$app->params['frp_url'] = 'http://'.Merchant::localInfo($mch_id,'frp_url').'/frp.php?token='.$token.'&';
        //return self::FRP_STATUS;
        if(!$val){
            if(!\Yii::$app->redis->hget('frp_status', $mch_id)){
                return 0;
            }
            return self::FRP_STATUS;
        }
        return 1;
    }
    /*
    public static function local_init_table($mch_id, $table){
        //$table = 'cloud_desktop,conf,gift,gift_goods,goods,goods_banner,goods_mat,goods_tags,hd_gift,huodong,material,mchuser,mchuser_points,tags,user_award,user_behavior,user_consumes,user_gift,user_playtime,user_process,user_togo,voice';
        $data = '?c=table&a=init&async=1&uid='.$mch_id.'&tb='.$table;
        $http = new HttpClient();
        return $http->sendRequest(self::getConfig('task_url').$data,'GET');
    }
    */
    public static $localNotice = true;
    /**
     * 本地拉取数据通知  local_pull_table
     * @param int $mch_id
     * @param string $name             表名 多个使用","分隔，id,fields将无效
     * @param int $id           主键id 为0侧是摘取所有 不为0只拉取指定id的数据
     * @param string $fields    指定要拉取的字段
     * @param string $type
     * @return bool|mixed
     */
    public static function local_pull_table($mch_id, $name, $id=0, $fields='',$type='', $isAsync = 1){
        if(!self::$localNotice) return true;
        //self::pullTable($mch_id, $name);
        if(!is_array($mch_id)){
            //加入检测本地服务端是否启动
            if(!($localStatus = self::is_local_login($mch_id))) {
                return false;
            }
            $where = self::makeWhere($mch_id, $id);
            $data = '?c=table&a=pull&async='.$isAsync.'&uid='.$mch_id.'&tb='.$name.'&type='.$type.'&fields='.$fields.'&id='.($where==''?$id:0).($where==''?'':'&where='.urlencode($where));
        }else{
            $localStatus = 1;
            if(!\Yii::$app->redis->exists('srv_token')){
                \Yii::$app->redis->setex('srv_token', 600, DataHelper::random(16,'01')); //10分钟
            }
            $data = '?c=base&a=sys_notice&srv_token='.\Yii::$app->redis->get('srv_token');
            $data .= '&uids='.implode(',', $mch_id).'&async='.$isAsync.'&toc=table&toa=pull&tb='.$name.'&type='.$type.'&fields='.$fields.'&id='.$id;
        }

        return self::localRequest($localStatus, $data);
    }
    //通知本地端删除表数据
    public static function local_del_table($mch_id, $name, $id=0, $isAsync = 1){
        if(!self::$localNotice) return true;
        //self::pullTable($mch_id, $name);
        //加入检测本地服务端是否启动
        if(!($localStatus = self::is_local_login($mch_id))) {
            return false;
        }

        $where = self::makeWhere($mch_id, $id);
        $data = '?c=table&a=del&async='.$isAsync.'&uid='.$mch_id.'&data='.urlencode('{"tb":"'.$name.'","id":"'.($where==''?$id:0).'","where":'.($where==''?'""':$where).'}');

        return self::localRequest($localStatus, $data);
    }
    public static function pullTable($mch_id, $name=null){
        $tables = \Yii::$app->redis->hget('pull_table', $mch_id);
        if($name===null) {
            \Yii::$app->redis->hdel('pull_table', $mch_id);
            return $tables;
        }
        if ($tables && strpos(',' . $tables . ',', ',' . $name . ',') !== false) return;
        \Yii::$app->redis->hset('pull_table', $mch_id, $tables ? $tables . ',' . $name : $name);
    }
    /*
    //通知本地端执行Sql
    public static function local_sql_table($mch_id, $sql=''){
        //加入检测本地服务端是否启动
        if(!self::is_local_login($mch_id)) return false;

        $data = '?c=table&a=sql&async=1&uid='.$mch_id.'&sql='.urlencode($sql);
        return self::localRequest($localStatus, $data);
    }*/
    public static function send_all_local($data){
        if(!\Yii::$app->redis->exists('srv_token')){
            \Yii::$app->redis->setex('srv_token', 600, DataHelper::random(16,'01')); //10分钟
        }
        $data = $data.'&async=1&c=base&a=sys_notice&srv_token='.\Yii::$app->redis->get('srv_token');
        //通知本地服务端
        return self::send_local($data);
    }
    //发送通知到本地服务端
    public static function send_local($data){
        //此通知方法不用检测本地服务端状态
        $http = new HttpClient();
        $result = $http->sendRequest(self::getConfig('task_url').'?'.$data,'GET', [], false, 5);

        parse_str($data, $var);
        if(!empty($var['uid'])){
            $localStatus = self::is_local_login($var['uid']);
            if($localStatus==self::FRP_STATUS){
                $data  = ['url'=>Yii::$app->params['frp_url'],'data'=>$data];
                $result = $http->sendRequest(self::getConfig('task_url').'?c=sys&a=curl&async=1&data='.urlencode(self::toJson($data)),'GET', [], false, 5);
            }
        }
        return $result;
    }
    //通知本地端执行操作  $ca = c=xxx&a=xxx[&data=urlecode({})]
    public static function local_do_ca($mch_id, $ca){
        //加入检测本地服务端是否启动
        if(!($localStatus = self::is_local_login($mch_id))) return false;
        $data = '?'.$ca.'&async=1&uid='.$mch_id;

        return self::localRequest($localStatus, $data);
    }
    public static function localVoice($mch_id, $content, $arg=[], $times=1){
        $data = [
            'c'=>'voice','a'=>'call','type'=>'voice',
            'data'=>[
                '类型'=>'手动播报',"内容"=>$content,'信息'=>ArrayHelper::getValue($arg, "信息", []),
                "次数" => $times,
                "前缀"=> ArrayHelper::getValue($arg, "前缀", ''), "后缀"=> ArrayHelper::getValue($arg, "后缀", ''),"背景"=> ArrayHelper::getValue($arg, "背景", ''), "提示音"=>ArrayHelper::getValue($arg, "提示音", "提示音(默认)|music")
            ]
        ];
        if(isset($arg["通道"])) $data['data']["通道"] = $arg["通道"];
        if(isset($arg["手台"])) $data['data']["手台"] = $arg["手台"];
        if(isset($arg["喇叭"])) $data['data']["喇叭"] = $arg["喇叭"];

        return self::send_local('c=sys&a=notice&async=1&toc=msgto&toa=type&touid=voice&async=1&uid='.$mch_id.'&data='.urlencode(DataHelper::toJson($data)));
    }
    public static function localOpenUrl($mch_id, $url, $data=''){
        $send_data = [
            'msgto'=>[
                'api'=>['openurl'] //curl
            ],
            'data'=>[
                'url'=>DataHelper::getConfig('local_url').$url,
            ]
        ];
        if($data) $send_data['data']['data'] = $data;
        //通知本地服务端
        return self::send_local('c=sys&a=notice&async=1&uid='.$mch_id.'&data='.urlencode(DataHelper::toJson($send_data)));
    }
    public static function localAllOpenUrl($url, $data=''){
        $send_data = [
            'msgto'=>[
                'api'=>['openurl'] //curl
            ],
            'data'=>[
                'url'=>DataHelper::getConfig('local_url').$url,
            ]
        ];
        if($data) $send_data['data']['data'] = $data;
        if(\Yii::$app->redis->get('queue:OpenUrl')) return false; //有队列在执行
        \Yii::$app->redis->setex('queue:OpenUrl', 86400, 1);
        //通知本地服务端
        return self::send_all_local('queue=OpenUrl&toc=sys&toa=notice&data='.urlencode(DataHelper::toJson($send_data)));
    }
    //通知本地控制台提示消息
    public static function localConsoleTips($mch_id, $msg, $tit='消息提示', $time=6){
        $data = ['c'=>'pop','a'=>'tishikuang','data'=>['title'=>$tit,'msg'=>$msg,'time'=>$time]];
        self::send_local('c=sys&a=notice&toc=msgto&toa=type&touid=console&async=1&uid='.$mch_id.'&data='.urlencode(DataHelper::toJson($data)));
    }
    private static function localRequest($localStatus, $data){
        $http = new HttpClient();
        if($localStatus==self::FRP_STATUS){
            $data = ['url' => Yii::$app->params['frp_url'], 'data' => $data[0] == '?' ? substr($data, 1) : $data];
            return $http->sendRequest(self::getConfig('task_url').'?c=sys&a=curl&async=1&data='.urlencode(self::toJson($data)),'GET', [], false, 5);
        }else{
            return $http->sendRequest(self::getConfig('task_url').$data,'GET', [], false, 5);
        }
    }

    /**
     * 检查当前时间是否在一个时间段内
     * @param $start
     * @param $end
     * @param null $time
     * @return bool
     */
    public static function isInTime($start, $end, $time = null)
    {
        if ( !$start ) {
            return true;
        }

        !is_int($start) && $start = strtotime(self::date(null, "Y-m-d " . $start));
        if ( !is_int($end) && $end ) {
            $end = strtotime(self::date(null, "Y-m-d " . $end));
        }
        !$time && $time = time();

        if ( $time >= $start ) {
            if ( !$end ) {
                return true;
            }
            if ( $time <= $end ) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取指定时间范围内的所有日期列表
     *
     * @param $format
     * @param $begin
     * @param $end
     * @return array|null
     */
    public static function getDates($format, $begin, $end)
    {
        if (!is_numeric($begin) || !is_numeric($end) || ($end <= $begin)) return null;
        $arr = [];
        for ($i = $begin; $i <= $end; $i += (24 * 3600)) {
            $arr[] = date($format, $i);
        }
        return $arr;
    }

    public static function getWeek($time = null)
    {
        !$time && $time = time();
        $week = date("w", $time);

        if ( $week == 0 ) {
            $week = 7;
        }

        return $week;
    }
    /** 递归合并数组
     * @param array $arr1 目标数组
     * @param array $arr2 合并数组
     * @param bool $strict 严格验证
     */
    public static function array_walk_merge(&$arr1, $arr2, $strict=false){
        foreach ($arr2 as $k=>$v){
            if(substr($k,0,1)=='@') continue;
            if($strict){
                $t1 = isset($arr1[$k])?gettype($arr1[$k]):'NULL';
                $t2 = gettype($v);
                if($t1!=$t2){
                    $arr1[$k] = $v;
                }elseif($t1=='array'){
                    self::array_walk_merge($arr1[$k], $v, $strict);
                }
            }else{
                $updateK = '@' . $k;
                if (!isset($arr1[$k]) || (isset($arr2[$updateK]) && (!isset($arr1[$updateK]) || $arr1[$updateK] != $arr2[$updateK]))) {
                    $arr1[$k] = $v;
                }
                elseif(is_array($v) && is_array($arr1[$k])){
                    self::array_walk_merge($arr1[$k], $v, $strict);
                }
            }
        }
    }

    /*static public function  getIp() {
        if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) {
            $ip = getenv("HTTP_CLIENT_IP");
        }
        else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")){
            $ip = getenv("HTTP_X_FORWARDED_FOR");
        } else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) {
            $ip = getenv("REMOTE_ADDR");
        } else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) {
            $ip = $_SERVER['REMOTE_ADDR'];
        } else {
            $ip = "unknown";
        }
        return $ip;
    }*/

    static public function getIp() {
        $unknown = 'unknown';
        if ( isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] && strcasecmp($_SERVER['HTTP_X_FORWARDED_FOR'], $unknown) ) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } elseif ( isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], $unknown) ) {
            $ip = $_SERVER['REMOTE_ADDR'];
        } else {
            $ip = Yii::$app->request->userIP;
        }

        /*
        处理多层代理的情况
        或者使用正则方式：$ip = preg_match("/[\d\.]{7,15}/", $ip, $matches) ? $matches[0] : $unknown;
        */
        if (false !== strpos($ip, ',')) {
            $ip = explode(',', $ip);
            $ip = reset($ip);
        }
        return $ip;
    }
    public static function name2sn($name, $len=10){
        $py = new Py();
        $letter = $py->str2py($name, false);
        $lLen = floor($len/2);
        $sLen = strlen($letter);
        if($sLen>$lLen) $letter = substr($letter, 0, $lLen);
        else $lLen = $sLen;
        $rLen = $len-$lLen;
        $number = substr(str_replace('.','', microtime(true)), -$rLen);
        return $letter.$number;
    }
    public static function SN18(){
        return date("YmdHis") . substr(microtime(),2,2) . rand(10, 99);
    }
    //8位前缀
    public static function prefix($mch_id, $s='1'){
        return $s . str_pad($mch_id, 7, '0', STR_PAD_LEFT);
    }
    //生成订单号
    public static function sn($mch_id, $s='1')
    {
        $chars = substr(microtime(),2,6);
        $sn = date('YmdHis').substr(str_shuffle($chars.$chars),0,6); //20位
        return self::prefix($mch_id, $s) . $sn;
    }
    public static function set59s($hi, $is59=true){
        //这里加59s
        if($is59){
            if(substr($hi,3)=='59:00'){
                $hi = substr($hi,0, 5).':59';
            }elseif(strlen($hi)==5){
                $hi = substr($hi,3)=='00' ? substr($hi,0, 5).':00':substr($hi,0, 5).':59';
            }
        }else{
            if(strlen($hi)==5){
                $hi = substr($hi,0, 5).':00';
            }
        }
        return $hi;
    }

    /**
     * @param int $start 运行时间开始标识
     * @param string $flag
     * @return mixed
     */
    public static function runtime($start=0, $flag=''){
        static $flags,$time;
        if($start) {
            if($flag){
                $time[$flag] = microtime(TRUE);
                $flags[$flag]['run0'] = number_format($time[$flag] -$time['__all'], 4);
            }else{
                $time['__all'] = microtime(TRUE);
                $flags = [];
            }
            return 0;
        }
        if($flag){
            $flags[$flag]['self'] = number_format(microtime(TRUE) - $time[$flag], 4);
            $flags[$flag]['run'] = number_format(microtime(TRUE) - $time['__all'], 4);
            return $flags[$flag]['run'];
        }
        $flags['__all'] = number_format(microtime(TRUE) - $time['__all'], 4);
        unset($time);
        return $flags;
    }

    /**
     * 格式化秒
     * @param $format
     * @param $seconds
     * @param bool $full
     * @return string
     */
    public static function secondFormat($format, $seconds, $full = false) {
        $format = $format ?: 'D天H时M分S秒';
        $day = floor($seconds / 86400);
        $hour = floor($seconds % 86400 / 3600);
        $minute = floor($seconds % 86400 % 3600 / 60);
        $second = floor($seconds % 86400 % 3600 % 60);

        if ($full) {
            $keyword = preg_replace('/[^DHMS]/', '', $format);
            $keywords = str_split($keyword);
            asort($keywords);

            $max = array_shift($keywords);
            if ($max == 'H') {
                $hour = floor($seconds / 3600);
            } elseif ('M') {
                $minute = floor($seconds / 60);
            } elseif ('S') {
                $second = $seconds;
            }
        }

        return strtr($format, ['D' => $day, 'H' => $hour, 'M' => $minute, 'S' => $second]);
    }
    /** 时间范围分段 年 月 日
     * @param int $start  开始时间
     * @param int $end    结束时间
     * @param string $type 分段方式 年year 月month 日day
     * @return array
     */
    public static function dateSplit($start, $end, $type='month'){
        //获取提成月份
        $dList = [];
        if($type=='month'){
            if(date("Y-m", $start)==date("Y-m", $end)){
                $dList = [date("Y-m", $start)=>['start'=>$start, 'end'=>$end]];
            }else{
                $starYm = date("Y-m", $start);
                $dList[$starYm] = ['start'=>$start, 'end'=> strtotime($starYm . '-' . date('t', $start) . ' 23:59:59')];
                $ymStart = strtotime('+1 month', strtotime($starYm));
                while($ymStart<$end){
                    $v = date("Y-m", $ymStart);
                    $dList[$v] = ['start' => strtotime($v . '-01 00:00:00'), 'end' => strtotime($v . '-' . date('t', $ymStart) . ' 23:59:59')];
                    $ymStart = strtotime('+1 month', $ymStart);
                }
                $dList[date("Y-m", $end)] = ['start'=>strtotime(date("Y-m", $end) . '-01 00:00:00'), 'end'=> $end];
            }
        }elseif($type=='day'){
            if(date("Y-m-d", $start)==date("Y-m-d", $end)){
                $dList = [date("Y-m-d", $start)=>['start'=>$start, 'end'=>$end]];
            }else{
                $starYmd = date("Y-m-d", $start);
                $dList[$starYmd] = ['start'=>$start, 'end'=> strtotime($starYmd . ' 23:59:59')];
                $ymdStart = strtotime($starYmd)+86400;
                while($ymdStart<$end){
                    $v = date("Y-m-d", $ymdStart);
                    $dList[$v] = ['start' => strtotime($v . ' 00:00:00'), 'end' => strtotime($v . ' 23:59:59')];
                    $ymdStart += 86400;
                }
                $dList[date("Y-m-d", $end)] = ['start'=>strtotime(date("Y-m-d", $end) . ' 00:00:00'), 'end'=> $end];
            }
        }elseif($type=='year'){
            if(date("Y", $start)==date("Y", $end)){
                $dList = [date("Y", $start)=>['start'=>$start, 'end'=>$end]];
            }else{
                $starY = date("Y", $start);
                $dList[$starY] = ['start'=>$start, 'end'=> strtotime($starY . '-12-' . date('t', $start) . ' 23:59:59')];
                $yStart = strtotime('+1 year', $start);
                while($yStart<$end){
                    $v = date("Y", $yStart);
                    $yEnd = strtotime($v.'-12-01');
                    $dList[$v] = ['start' => strtotime($v . '-01-01 00:00:00'), 'end' => strtotime($v . '-12-' . date('t', $yEnd) . ' 23:59:59')];
                    $yStart = strtotime('+1 year', $yStart);
                }
                $dList[date("Y", $end)] = ['start'=>strtotime(date("Y", $end) . '-01 00:00:00'), 'end'=> $end];
            }
        }

        foreach ($dList as &$v){
            $v['startFmt'] = date("Y-m-d H:i:s", $v['start']);
            $v['endFmt'] = date("Y-m-d H:i:s", $v['end']);
        }
        return $dList;
    }
    //对外接口通用签名
    public static function openSign($key, $uniqueId=''){
        $data = \Yii::$app->request->isPost ? \Yii::$app->request->post() : \Yii::$app->request->get();
        if(!isset($data["sign"])) return false;
        $sign = $data["sign"];
        unset($data["sign"]);
        ksort($data); //键名升序
        if($uniqueId){
            $md5 = md5(sprintf("%s+%s+%s", $uniqueId, http_build_query($data), $key)); //index/reg+a=1&mch_id=1&s=1+signkey;
        }else{
            $md5 = md5(sprintf("%s+%s", http_build_query($data), $key));
        }

        return $md5 == $sign;
    }
    /**
     * 分页初始
     * @param [int] $total 数据行数
     * @param int|string $num [每页数量|每页数,显示页码数]
     * @param string $parm [附加参数]
     * @param string $pName
     * @return array
     */
    public static function initPage($total, $cPage=1, $num = 10, $parm = '', $pName='page') {
        $initPageNum = 5;
        if (strpos($num, ',')) {
            list($num, $initPageNum) = explode(',', $num);
        }
        $pCount = $total ? ceil($total / $num) : 1;
        if ($cPage < 1) $cPage = 1;
        if ($cPage > $pCount) $cPage = $pCount;
        $prev = $cPage > 1 ? $cPage - 1 : ''; //上一页
        $next = $cPage < $pCount ? $cPage + 1 : ''; //下一页
        $path = Yii::$app->request->baseUrl;
        $path = $path != '/' ? rtrim($path, '/') : $path;
        $qstr = !empty($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
        if (strpos($qstr, $pName) !== false) {
            $qstr = preg_replace("/$pName=\d{1,}/i", '', $qstr);
            $qstr = str_replace('&&', '&', trim($qstr, '&'));
        }
        $pages = ['size'=>$num,'perPage'=>$num, 'total' => $total,'totalCount'=>$total, 'cPage' => $cPage,'currentPage'=>$cPage, 'pCount' => $pCount,'pageCount'=>$pCount, 'pNum' => array()];
        //获取页码范围
        if ($cPage <= 1) {
            $TmpPageNo = 1;
            $TmpPageNum = $initPageNum;
        } elseif ($cPage <= $initPageNum) {
            $TmpPageNo = 1;
            $TmpPageNum = $initPageNum + $cPage - 1;
        } elseif ($cPage > $initPageNum && $cPage < $pCount) {
            $TmpPageNo = $cPage - $initPageNum;
            $TmpPageNum = $initPageNum + $cPage - 1;
        } elseif ($cPage >= $pCount) {
            $TmpPageNo = $cPage - $initPageNum;
            $TmpPageNum = $pCount;
        }
        //页码超出范围
        if ($TmpPageNum > $pCount) $TmpPageNum = $pCount;
        for ($PageNo = $TmpPageNo; $PageNo <= $TmpPageNum; $PageNo++) {
            $pages['pNum'][$PageNo] = $cPage == $PageNo ? 'javascript:' : "$path?$pName=$PageNo" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');
        }
        $pages['first'] = $prev == '' ? 'javascript:' : "$path?$pName=1" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');
        $pages['prev'] = $prev == '' ? 'javascript:' : "$path?$pName=$prev" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');
        $pages['nopage'] = "$path?" . ($qstr != '' ? $qstr . '&' : '') . ($parm != '' ? $parm . '&' : '') . "$pName="; //可自行在尾部补加页数
        $pages['curr'] = "$path?$pName=$cPage" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');
        $pages['next'] = $next == '' ? 'javascript:' : "$path?$pName=$next" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');
        $pages['last'] = $next == '' ? 'javascript:' : "$path?$pName=$pCount" . ($qstr != '' ? '&' . $qstr : '') . ($parm != '' ? '&' . $parm : '');

        return $pages; //当前页码 总页码 总数 上一页链接 下一页链接
    }
    //获取分页偏移值
    public static function getOffset($init=1, $num=10, $step=0)
    {
        if ($init < 1) $init = 1; //验证（>＝1）
        if ($init == 1) {
            $offset =  [$step , $num];
        } else {
            $offset = [(($init - 1) * $num + $step) , $num];
        }
        return $offset[0];
    }
}